package tech.dubbo.client.factoryBean;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import tech.dubbo.api.dto.UserReqDTO;
import tech.dubbo.api.dto.UserResDTO;
import tech.dubbo.api.types.Request;
import tech.dubbo.api.types.Response;
import tech.dubbo.client.codec.JsonDecoder;
import tech.dubbo.client.codec.JsonEncoder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * @ClassName
 * @Author niujie
 * @Version
 * @Description
 * @CreateTime 2024/9/28
 */
public class InterfacesFactoryBean<T> implements FactoryBean<T>, InitializingBean {

    private Channel channel;

    private Class<T> interfaces;

    private Map<String, Promise> promiseMap = new ConcurrentHashMap<>();

    private static AtomicInteger atomic = new AtomicInteger(0);

    public void setInterfaces(Class<T> interfaces)
    {

        this.interfaces = interfaces;
    }

    @Override
    public T getObject() throws Exception
    {

      return  (T) Proxy.newProxyInstance(interfaces.getClassLoader(), new Class[]{interfaces}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Map<String, Object> invocation = new HashMap<>();
                invocation.put("clazz", interfaces);
                invocation.put("methodName", method.getName());
                invocation.put("paramTypes", method.getParameterTypes());

                Request<Object> req = new Request<>();
                req.setSerializeId(String.valueOf(atomic.incrementAndGet()));
                req.setArgs(args);
                req.setData(invocation);

                DefaultPromise<Object> promise = new DefaultPromise<>(channel.eventLoop());
                promiseMap.put(req.getSerializeId(), promise);

                channel.writeAndFlush(req);

                promise.await();

                if (promise.isSuccess()) {
                    Response response = (Response)promise.getNow();

                    return response;
                }else {
                    throw new RuntimeException("");
                }
            }
        });
    }

    @Override
    public Class<?> getObjectType()
    {

        return interfaces;
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {

        NioEventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new JsonEncoder());
                        ch.pipeline().addLast(new JsonDecoder<>(UserResDTO.class));
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<Response>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, Response data) throws Exception {

                                Promise promise = promiseMap.get(data.getSerializeId());

                                promise.setSuccess(data);
                            }
                        });
                    }
                });


        Channel channel = bootstrap.connect("localhost",8085).sync().channel();

        this.channel = channel;

        channel.closeFuture().addListener((ChannelFuture future) -> {

            group.shutdownGracefully();
        });
    }
}
